using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Drawing.Imaging;

namespace SmartControls
{
	public abstract class OwnerDrawnList : Control
	{
		#region "Private Variables"
		VScrollBar scrollBar;
		Bitmap BackBuffer;
		int itemHeight;

		private CurrencyManager cm;
		private string m_ValueMember;
		private string m_DisplayMember;
		private object m_DataSource;
		private IList m_Items;
		#endregion
		#region "Data Binding"
		public string ValueMember
		{
			get
			{
				return m_ValueMember;
			}
			set
			{
				m_ValueMember = value;
			}
		}

		public string DisplayMember
		{
			get
			{
				return m_DisplayMember;
			}
			set
			{
				m_DisplayMember = value;
			}
		}

		public object DataSource
		{
			get
			{
				return m_DataSource;
			}
			set
			{
				if (m_DataSource != value)
				{
					if (value == null)
					{
						cm.PositionChanged -= new EventHandler(cm_PositionChanged);
						cm.ListChanged -= new ListChangedEventHandler(cm_ListChanged);
						cm = null;
						m_Items = new object[0];
					}
					else
					{
						if (cm != null)
						{
							cm.PositionChanged -= new EventHandler(cm_PositionChanged);
							cm.ListChanged -= new ListChangedEventHandler(cm_ListChanged);
						}
						if (!(value is IList | value is IListSource))
						{
							//not a valid data source for this purpose
							throw new System.Exception("Invalid DataSource");
						}
						else
						{
							if (value is IListSource)
							{
								IListSource myListSource;
								myListSource = (IListSource)value;
								if (myListSource.ContainsListCollection == true)
									throw new System.Exception("Invalid DataSource");
							}
							m_DataSource = value;
							cm = (CurrencyManager)this.BindingContext[value];
							m_Items = cm.List;
							cm.PositionChanged += new EventHandler(cm_PositionChanged);
							cm.ListChanged += new ListChangedEventHandler(cm_ListChanged);
							RecalcScrollBar();
						}
					}
				}
			}
		}

		public object GetValue(int Index)
		{
			return GetProperty(Index, ValueMember);
		}

		public object GetDisplay(int Index)
		{
			return GetProperty(Index, DisplayMember);
		}
		public object GetProperty(int Index, string Property)
		{
			if ((cm != null))
			{
				if ((Property != "") && (Index >= 0 & (0 < Items.Count)))
				{
					PropertyDescriptor pdMember;
					pdMember = cm.GetItemProperties()[Property];
     				return pdMember.GetValue(Items[Index]);
				}
			}
			return null;
		}
		#endregion
		#region "Constructors"
		public OwnerDrawnList() : base()
		{
			// Create the scroll bar
			scrollBar = new VScrollBar();
			scrollBar.ValueChanged += new System.EventHandler(this.scrollBar_ValueChanged);
			this.Controls.Add(scrollBar);

			// TODO: Figure out how to add context menu support

			DisplayMember = "";
			m_Items = new object[0];
			this.Size = new Size(224, 200);
			RecalcItemHeight();
		}
		#endregion
		#region "Events"
		public event DrawItemEventHandler DrawItem;
		protected virtual void OnDrawItem(object sender, DrawItemEventArgs e)
		{
		    if (this.DrawItem != null)
		        this.DrawItem(sender, e);
		}

		public event EventHandler SelectedIndexChanged;
		protected virtual void OnSelectedIndexChanged(EventArgs e)
		{
            this.EnsureVisible(SelectedIndex);
            this.Refresh();
            if (this.SelectedIndexChanged != null)
				this.SelectedIndexChanged(this, e);
		}

		protected override void OnResize(EventArgs e)
		{
			base.OnResize(e);
			scrollBar.Left = this.Width - scrollBar.Width;
			scrollBar.Height = this.Height;
			BackBuffer = new Bitmap(this.Width - 1 - (scrollBar.Visible?scrollBar.Width:1), this.Height - 2);
		}
		protected override void OnKeyDown(KeyEventArgs e)
		{
			switch (e.KeyCode)
			{
				case Keys.Down:
					if (this.SelectedIndex < this.scrollBar.Maximum)
					{
						++this.SelectedIndex;
					}
					break;
				case Keys.Up:
					if (this.SelectedIndex > this.scrollBar.Minimum)
					{
						--this.SelectedIndex;
					}
					break;
				case Keys.PageDown:
					this.SelectedIndex = Math.Min(this.scrollBar.Maximum, this.SelectedIndex + scrollBar.LargeChange + 1);
					break;
				case Keys.PageUp:
					this.SelectedIndex = Math.Max(this.scrollBar.Minimum, this.SelectedIndex - this.scrollBar.LargeChange);
					break;
				case Keys.Home:
					this.SelectedIndex = 0;
					break;
				case Keys.End:
					this.SelectedIndex = this.Items.Count - 1;
					break;
			}

			base.OnKeyDown(e);
		}
		protected override void OnMouseDown(MouseEventArgs e)
		{
			//get out if there are no items
			if (Items.Count == 0)
				return;
			SelectedIndex = this.scrollBar.Value + (e.Y / this.ItemHeight);
			this.Focus();
		}
		protected virtual void OnFontChanged(System.EventArgs e)
		{
			RecalcItemHeight();
			Invalidate();
		}


		#endregion
		#region "Keeping Everything In Sync"
		private void cm_PositionChanged(object sender, System.EventArgs e)
		{
			OnSelectedIndexChanged(e);
			Invalidate();
		}
		void cm_ListChanged(object sender, ListChangedEventArgs e)
		{
			RecalcScrollBar();
			Invalidate();
		}
		#endregion
		#region "Painting"
		protected virtual void RecalcItemHeight()
		{
			Graphics g = this.CreateGraphics();
			itemHeight = g.MeasureString("W", this.Font).ToSize().Height;
			g.Dispose();
			RecalcScrollBar();
		}

		protected override void OnPaintBackground(System.Windows.Forms.PaintEventArgs e)
		{
			//Do nothing
		}

		protected override void OnPaint(PaintEventArgs e)
		{
			e.Graphics.DrawRectangle(new Pen(this.ForeColor), 0, 0, this.Width-1, this.Height-1);
			
			Graphics g = Graphics.FromImage(BackBuffer);
			g.Clear(this.BackColor);

			// draw items that are visible
			for (int curItem = scrollBar.Value; (curItem < Items.Count) && ((curItem-scrollBar.Value) * ItemHeight < Height); curItem++ )
				PaintItem(g, curItem);

			e.Graphics.DrawImage(BackBuffer, 1, 1);
			e.Graphics.Dispose();
		}

		protected void PaintItem(Graphics graphics, int Index)
		{
			// Calculate the drawing area for the item 
			Rectangle rect = new Rectangle(0, (Index - scrollBar.Value) * itemHeight, BackBuffer.Width, itemHeight);

			// Draw the item
			//graphics.Clip = new Region(rect);
			OnDrawItem(this,
				new DrawItemEventArgs(graphics, this.Font, rect, Index, (SelectedIndex == Index) ? DrawItemState.Selected : DrawItemState.None));
		}

		#endregion
		#region "ScrollBar"
		protected void scrollBar_ValueChanged(object sender, System.EventArgs e)
		{
			Invalidate();
		}
		protected void RecalcScrollBar()
		{
			scrollBar.Minimum = 0;
			scrollBar.Maximum = m_Items.Count;
			scrollBar.LargeChange = Math.Max(this.Height / ItemHeight - 1, 1);
			scrollBar.Visible = (this.Height / ItemHeight < m_Items.Count);
			BackBuffer = new Bitmap(this.Width - 1 - (scrollBar.Visible ? scrollBar.Width : 1), this.Height - 2);
		}
		#endregion
		#region "Properties"
		public IList Items
		{
			get
			{
				return m_Items;
			}
		}
		public virtual int SelectedIndex
		{
			get
			{
				return (cm!=null)?cm.Position:-1;
			}
			set
			{
				if (cm != null)
				{
					cm.Position = value;
				}
			}
		}
		protected virtual int ItemHeight
		{
			get
			{
				return itemHeight;
			}
			set
			{
				itemHeight = value;
			}
		}
		public override Font Font
		{
			get
			{
				return base.Font;
			}
			set
			{
				base.Font = value;
				OnFontChanged(EventArgs.Empty);
			}
		}
		#endregion
		public void EnsureVisible(int index)
		{
			if (index < this.scrollBar.Value)
			{
				scrollBar.Value = index;
				this.Invalidate();
			}
			else if (index >= scrollBar.Value + scrollBar.LargeChange + 1)
			{
				scrollBar.Value = index - scrollBar.LargeChange;
				this.Invalidate();
			}
		}
	}

	public delegate void DrawItemEventHandler(object sender, DrawItemEventArgs e);

	public enum DrawItemState
	{
		None = 0,
		Selected = 1,
		Disabled = 4,
		Focus = 16
	}

	public class DrawItemEventArgs : EventArgs
	{
		Graphics graphics;
		Font font;
		Rectangle rect;
		int index;
		DrawItemState state;

		public DrawItemEventArgs(Graphics Graphics, Font Font, Rectangle Rect, int Index, DrawItemState State) : base()
		{
			this.graphics = Graphics;
			this.font = Font;
			this.rect = Rect;
			this.index = Index;
			this.state = State;
		}

		public Rectangle Bounds
		{
			get
			{
				return rect;
			}
		}

		public virtual void DrawBackground()
		{
			Brush brush;
			if (state == DrawItemState.Selected)
			{
				brush = new SolidBrush(SystemColors.Highlight);
			}
			else
			{
				brush = new SolidBrush(SystemColors.Control);
			}		
			graphics.FillRectangle(brush, rect);
		}

		/// <summary>
		/// Draws the background within the bounds specified in the DrawItemEventArgs constructor and with the appropriate color.
		/// </summary>
		public virtual void DrawBackground(Color color)
		{
			Brush brush;
			brush = new SolidBrush(color);
			graphics.FillRectangle(brush, rect);
		}

		public DrawItemState State
		{
			get{return state;}
		}

		public Graphics Graphics
		{
			get{return graphics;}
		}

		public int Index
		{
			get{return index;}
		}

		public System.Drawing.Font Font
		{
			get{return font;}
		}
	}
}
